/*
 * This file is part of b.AMP.
 *
 * b.AMP is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * b.AMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with b.AMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __BAMP_VALUE_HPP__
#define __BAMP_VALUE_HPP__

#include <bamp/value.h>
#include <bamp/exception.hpp>

#include <cstring>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>

#include <stdint.h>


namespace bamp {

    /**
     * Type IDs for the types held by Value objects.
     */
    typedef bamp_type Type;

    
    /**
     * The Value class is a wrapper around the bamp_value
     * structure. It implements basic dynamic typing. It is used to
     * create dynamic argument lists.
     */
    class Value
    {
    public:

        /**
         * Initialize a new value by setting it to the null value.
         */
        Value()
        {
            BAMP_RC_THROW(bamp_value_new(&m_data));
        }

        /**
         * Decrement the value's reference counter and destroy it if
         * there are no more references. Using the raw C pointer to
         * the value after it has been freed yields undefined
         * behavior.
         */
        ~Value()
        {
            // Avoid throwing an exception from a destructor
            bamp_value_unref(m_data);
        }

        /**
         * Reference a value and increment its reference counter.
         */
        Value(const Value& value)
        {
            m_data = value.m_data;
            BAMP_RC_THROW(bamp_value_ref(m_data));
        }

        /**
         * Reference a value and increment its reference counter.
         */
        Value(bamp_value* value)
        {
            m_data = value;
            BAMP_RC_THROW(bamp_value_ref(m_data));
        }

        /**
         * Create a new copy of a value. The value cannot be simply
         * referenced because it is const and we are not.
         */
        Value(const bamp_value* value)
        {
            BAMP_RC_THROW(bamp_value_clone(&m_data, value));
        }

        /**
         * Free this value and then copy another one into it.
         * @param value the other value
         */
        Value& operator=(const Value& value)
        {
            if (m_data != value.m_data)
            {
                m_data = value.m_data;
                BAMP_RC_THROW(bamp_value_ref(m_data));
            }
            return *this;
        }

        /**
         * Compare the value with another one.
         */
        bool operator==(const Value& rhs) const
        {
            // Two values with different types cannot be the same
            if (m_data->type != rhs.m_data->type) return false;

            // All null values are equal
            if (m_data->type == BAMP_TYPE_NULL)  return true;

            // Strings are a special case: we must compare the actual
            // string and not the char* pointer
            if (m_data->type == BAMP_TYPE_STRING)
                return strcmp(m_data->value.str, rhs.m_data->value.str) == 0;

            // Integer and floating-point values are compared bitwise
            return memcmp(&(m_data->value), &(rhs.m_data->value),
                          sizeof(m_data->value)) == 0;
        }

        /**
         * Initialize the Value object with the actual value
         * given. The value type must be one of the types supported by
         * the get()/set() methods. This method can be used for
         * implicit conversion from one of the types supported to a
         * Value object.
         */
        template<typename T>
        Value(const T& value)
        {
            BAMP_RC_THROW(bamp_value_new(&m_data));
            BAMP_RC_THROW(__set(value));
        }


        /**
         * Initialize the value by converting from any type that
         * implements operator<<.
         */
        template<typename T1, typename T2>
        Value(const T1& type, const T2& value)
        {
            // Convert the type to Type
            Type t;
            {
                std::ostringstream os;
                os << type;
                const char* cstr = os.str().c_str();
                BAMP_RC_THROW(bamp_value_type_from_str(&t, cstr));
            }

            // Allocate the value
            {
                std::ostringstream os;
                os << value;
                const char* cstr = os.str().c_str();
                BAMP_RC_THROW(bamp_value_new_from_str(&m_data, cstr, t));
            }
        }     


        /**
         * Implicit conversion operator template. This makes implicit
         * conversion from a Value object to one of the types supported
         * possible.
         */
        template<typename T>
        operator T() const
        {
            T value;
            BAMP_RC_THROW(__get(value));
            return value;
        }

        /**
         * Create a complete copy of the value held.
         */
        Value clone() const
        {
            bamp_value* copy;
            BAMP_RC_THROW(bamp_value_clone(&copy, m_data));
            return copy;
        }

        /**
         * @param value the value held
         */
        template<typename T>
        void get(T& value) const
        {
            BAMP_RC_THROW(__get(value));
        }

        /**
         * @return the value held
         */
        template<typename T>
        T get() const
        {
            T value;
            BAMP_RC_THROW(__get(value));
            return value;
        }

        /**
         * @param a new value to hold
         */
        template<typename T>
        void set(const T& value)
        {
            BAMP_RC_THROW(__set(value));
        }
        
        /**
         * @return type of the value
         */
        Type getType() const
        {
            return m_data->type;
        }

        /**
         * @return true if the value is the null value.
         */
        bool isNull() const
        {
            int is_null;
            BAMP_RC_THROW(bamp_value_is_null(m_data, &is_null));
            return is_null != 0;
        }

        /**
         * Reset the value to the null value.
         */
        void setNull()
        {
            BAMP_RC_THROW(bamp_value_set_null(m_data));
        }


        /**
         * Convert the value to a string.
         */
        std::string string() const
        {
            char* cstr;
            BAMP_RC_THROW(bamp_value_to_new_str(m_data, &cstr));
            std::string str(cstr);
            free(cstr);
            return str;
        }

        /**
         * Return the value type as a string.
         */
        std::string typeName() const
        {
            return typeName(m_data->type);
        }

        /**
         * Convert a Type enumerated value to a string.
         */
        static std::string typeName(Type type)
        {
            const char* cstr;
            BAMP_RC_THROW(bamp_value_type_to_str(type, &cstr));
            return std::string(cstr);
        }

        /**
         * Convert a string to the enumerated Type value it
         * represents.
         */
        static Type type(const std::string& name)
        {
            Type type;
            BAMP_RC_THROW(bamp_value_type_from_str(&type, name.c_str()));
            return type;
        }


    private:

        /*
         * Getters.
         */
        bamp_rc __get(uint8_t&  v) const
        { return bamp_value_get_u8 (m_data, &v); }

        bamp_rc __get(uint16_t& v) const
        { return bamp_value_get_u16(m_data, &v); }

        bamp_rc __get(uint32_t& v) const
        { return bamp_value_get_u32(m_data, &v); }

        bamp_rc __get(uint64_t& v) const
        { return bamp_value_get_u64(m_data, &v); }

        bamp_rc __get(int8_t&   v) const
        { return bamp_value_get_i8 (m_data, &v); }

        bamp_rc __get(int16_t&  v) const
        { return bamp_value_get_i16(m_data, &v); }

        bamp_rc __get(int32_t&  v) const
        { return bamp_value_get_i32(m_data, &v); }

        bamp_rc __get(int64_t&  v) const
        { return bamp_value_get_i64(m_data, &v); }

        bamp_rc __get(float&    v) const
        { return bamp_value_get_f32(m_data, &v); }

        bamp_rc __get(double&   v) const
        { return bamp_value_get_f64(m_data, &v); }

        bamp_rc __get(std::string& value) const
        {
            char* str;
            const bamp_rc rc = bamp_value_get_str(m_data, &str);
            value.assign(str);
            return rc;
        }

        /*
         * Setters.
         */
        bamp_rc __set(uint8_t  v) { return bamp_value_set_u8 (m_data, v); }
        bamp_rc __set(uint16_t v) { return bamp_value_set_u16(m_data, v); }
        bamp_rc __set(uint32_t v) { return bamp_value_set_u32(m_data, v); }
        bamp_rc __set(uint64_t v) { return bamp_value_set_u64(m_data, v); }
        bamp_rc __set(int8_t   v) { return bamp_value_set_i8 (m_data, v); }
        bamp_rc __set(int16_t  v) { return bamp_value_set_i16(m_data, v); }
        bamp_rc __set(int32_t  v) { return bamp_value_set_i32(m_data, v); }
        bamp_rc __set(int64_t  v) { return bamp_value_set_i64(m_data, v); }
        bamp_rc __set(float    v) { return bamp_value_set_f32(m_data, v); }
        bamp_rc __set(double   v) { return bamp_value_set_f64(m_data, v); }

        bamp_rc __set(const std::string& value)
        {
            return bamp_value_set_str(m_data, value.c_str());
        }
        

    public:

        bamp_value* cptr()             { return m_data; }
        const bamp_value* cptr() const { return m_data; }


    private:

        bamp_value* m_data;

    };

    static inline std::ostream& operator<<(std::ostream& os, const Value& v)
    {
        os << Value::typeName(v.getType()) << " " << v.string();
        return os;
    }

} // namespace bamp

#endif // __BAMP_VALUE_HPP__
